/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */

package cn.hermit.util;

import cn.hermit.reflection.parameter.CachingParameterNameFinder;
import cn.hermit.reflection.parameter.ParameterNameFinder;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Reflection utility
 *
 * @author Jack Gao (Chinese name : GAO JIANGUO, Email : linux.gjg@gmail.com)
 * @author JackGao 高建国
 * @date 18 Jun, 2014
 * @description //TODO 设计说明
 * @date 1/6/2017
 */
public class ReflectionUtils {

    /**
     * When {@code Type} initialized with a value of an object, its fully qualified class name
     * will be prefixed with this.
     *
     * @see {@link ReflectionUtils#getClassName(Type)}
     */
    private static final String TYPE_CLASS_NAME_PREFIX = "class ";
    private static final String TYPE_INTERFACE_NAME_PREFIX = "interface ";

    private static final ParameterNameFinder INSTANCE_PARAMETER_FINDER = new CachingParameterNameFinder();

    public static ParameterNameFinder getNameFinderSingleton() {
        return INSTANCE_PARAMETER_FINDER;
    }

    /**
     * Get a primitive class from Wrapper class if it is
     *
     * @param clz The target class
     * @return A primitive class
     */
    public static Class<?> getPrimitiveType(Class<?> clz) {
        if (clz.isPrimitive()) {
            return clz;
        } else {
            try {
                Field typeField = clz.getField("TYPE");
                Class<?> type = (Class<?>) typeField.get(null);
                return type;
            } catch (Exception e) {
                return null;
            }
        }
    }

    /**
     * Check if a class is a basic type in Java
     *
     * @param clz The class to check
     * @return
     */
    public static boolean isBaseType(Class<?> clz) {
        if (clz.isPrimitive() || Number.class.isAssignableFrom(clz)
                || Character.class.isAssignableFrom(clz) || String.class == clz) {
            return true;
        } else {
            try {
                Field typeField = clz.getField("TYPE");
                Class<?> type = (Class<?>) typeField.get(null);
                return type.isPrimitive();
            } catch (Exception e) {
                return false;
            }
        }
    }

    /**
     * Check if the object container is empty.
     *
     * @param obj The object container suck like collection or array or string.
     * @return
     */
    public static boolean isEmpty(Object obj) {
        if (obj == null)
            return true;
        if (Map.class.isInstance(obj)) {
            return ((Map<?, ?>) obj).isEmpty();
        } else if (Collection.class.isInstance(obj)) {
            return ((Collection<?>) obj).isEmpty();
        } else if (obj.getClass().isArray()) {
            return Array.getLength(obj) == 0;
        } else if (String.class.isInstance(obj)) {
            return "".equals(obj.toString().trim());
        }
        return false;
    }

    /**
     * Check if the object containers is empty.
     *
     * @param obj The object containers suck like collections or arrays or strings.
     * @return
     */
    public static boolean isAllEmpty(Object... obj) {
        if (isEmpty(obj)) {
            return true;
        }
        boolean result = true;
        for (Object o : obj) {
            result = result && isEmpty(o);
        }
        return result;
    }

    /**
     * Java wrapper type list
     */
    private static List<Class<?>> WRAPPER_TYPES = Arrays
            .asList(new Class<?>[]{Boolean.class, Character.class,
                    Byte.class, Short.class, Integer.class, Long.class,
                    Float.class, Double.class /* , Void.class */});

    /**
     * Check if an object is primitive
     *
     * @param obj An object
     * @return
     */
    public static boolean isPrimitive(Object obj) {
        if (obj == null)
            return false;
        return obj.getClass().isPrimitive() || isWrapperType(obj.getClass());
    }

    /**
     * Check if the class is Java wrapper type.
     *
     * @param clazz The class
     * @return
     */
    public static boolean isWrapperType(Class<?> clazz) {
        return WRAPPER_TYPES.contains(clazz);
    }

    /**
     * Get new object from heap but not the old object.
     *
     * @param obj The old object in heap stack.
     * @return New object
     */
    public static Object getNoCacheObject(Object obj) {
        if (obj == null)
            return null;
        Class<?> clz = obj.getClass();
        if (String.class == clz) {
            return new String(obj.toString());
        } else if (Character.class == clz) {
            Character v = (Character) obj;
            if (v >= -1 << 7 && v < (1 << 7) - 1) {
                return new Character(v);
            }
        } else if (Byte.class == clz) {
            Byte v = (Byte) obj;
            if (v >= -1 << 7 && v < (1 << 7) - 1) {
                return new Byte(v);
            }
        } else if (Short.class == clz) {
            Short v = (Short) obj;
            if (v >= -1 << 7 && v < (1 << 7) - 1) {
                return new Short(v);
            }
        } else if (Integer.class == clz) {
            Integer v = (Integer) obj;
            if (v >= -1 << 7 && v < (1 << 7) - 1) {
                return new Integer(v);
            }
        } else if (Long.class == clz) {
            Long v = (Long) obj;
            if (v >= -1 << 7 && v < (1 << 7) - 1) {
                return new Long(v);
            }
        }
        return obj;
    }

    /**
     * Parse string with given type
     *
     * @param clz The given type
     * @param str The string to parse
     * @return The object with given type
     * @throws Exception
     */
    public static Object parseString(Class<?> clz, String str) throws Exception {
        if (String.class.equals(clz)) {
            return str;
        } else if (int.class.equals(clz) || Integer.class.equals(clz)) {
            return Integer.parseInt(str);
        } else if (short.class.equals(clz) || Short.class.equals(clz)) {
            return Short.parseShort(str);
        } else if (byte.class.equals(clz) || Byte.class.equals(clz)) {
            return Byte.parseByte(str);
        } else if (long.class.equals(clz) || Long.class.equals(clz)) {
            return Long.parseLong(str);
        } else if (float.class.equals(clz) || Float.class.equals(clz)) {
            return Float.parseFloat(str);
        } else if (double.class.equals(clz) || Double.class.equals(clz)) {
            return Double.parseDouble(str);
        } else if (boolean.class.equals(clz) || Boolean.class.equals(clz)) {
            return Boolean.parseBoolean(str);
        } else if (clz.isEnum()) {
            return clz.getMethod("valueOf", new Class[]{String.class})
                    .invoke(clz, str);
        }
        return str;
    }

    /**
     * Guess the object type and return a suitable object with the guessed type
     *
     * @param value the object to guess
     * @return A suitable object with the guessed type
     */
    public static Object guessObject(String value) {
        if (value == null)
            return value;
        Object primitive = value;
        value = value.trim();
        if ("".equals(value)) {
            return primitive;
        }
        if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
            primitive = Boolean.valueOf(value);
            return primitive;
        }

        // maybe it is a BigDecimal
        if (Character.isDigit(value.charAt(0))
                && (value.indexOf('+') != -1 || value.indexOf('-') != -1)) {
            try {
                primitive = new BigDecimal(value);
                return primitive;
            } catch (Exception e) {

            }
        }

        try {
            primitive = Integer.valueOf(value);// maybe it is a integer
            return primitive;
        } catch (Exception e) {

        }
        try {
            primitive = Long.valueOf(value);// maybe it is a long
            return primitive;
        } catch (Exception e) {

        }
        try {
            primitive = new BigInteger(value);// maybe it is a BigInteger
            return primitive;
        } catch (Exception e) {

        }

        try {
            Double v = Double.valueOf(value);// maybe it is a double
            if (!v.isInfinite() && !v.isNaN()) {
                primitive = v;
            } else {
                primitive = value;// out of double scope, return string
            }
            return primitive;
        } catch (Exception e) {
        }

        return primitive;
    }

    /**
     * Append element to list-like container including Array, Collection
     *
     * @param object The list-like container
     * @param value  The element
     * @return The list-like container with new element
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static Object appendToListLikeObject(Object object, Object value)
            throws Exception {
        if (object.getClass().isArray()) {
            int length = Array.getLength(object);
            Object newObj = Array.newInstance(object.getClass()
                    .getComponentType(), length + 1);
            for (int i = 0; i < length; i++) {
                Object o = Array.get(object, i);
                Array.set(newObj, i, o);
            }
            Array.set(newObj, length, value);
            return newObj;
        } else if (Collection.class.isInstance(object)) {
            Collection col = (Collection) object;
            col.add(value);
            return object;
        } else {
            throw new Exception("Can not add Object to object typed["
                    + object.getClass().getName() + "]");
        }
    }

    /**
     * Get element from list-like container by index.
     *
     * @param object The list-like container
     * @param index  The index from where to getHttpRequest element
     * @return The element getHttpRequest from list-like container
     * @throws Exception
     */
    public static Object getFromListLikeObject(Object object, int index)
            throws Exception {
        if (object.getClass().isArray()) {
            int arrLength = Array.getLength(object);
            if (index >= arrLength) {
                return null;
            }
            return Array.get(object, index);
        } else if (List.class.isInstance(object)) {
            return ((List) object).get(index);
        } else if (Set.class.isInstance(object)) {
            int i = 0;
            Iterator iter = ((Set) object).iterator();
            while (iter.hasNext()) {
                if (i == index) {
                    return iter.next();
                }
                iter.next();
                i++;
            }
            return null;
        } else {
            throw new Exception("Can not getHttpRequest Object from object typed["
                    + object.getClass().getName() + "]");
        }
    }

    /**
     * Append key-value pair to map-like container, e.g Map, POJO .
     * <p>
     * Only support key with String type.
     *
     * @param object The map-like container
     * @param key    The key
     * @param value  The value
     * @return The map-like container with new key-value pair
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static Object appendToMapLikeObject(Object object, String key,
                                               Object value) throws Exception {
        if (Map.class.isInstance(object)) {
            ((Map) object).put(key, value);
            return object;
        } else if (String.class.equals(object.getClass())
                || isBaseType(object.getClass())) {
            throw new Exception("Can not append value to Object for type["
                    + object.getClass() + "]");
        } else {
            try {
                Field field = object.getClass().getDeclaredField(key);
                boolean originalAccess = field.isAccessible();
                try {
                    field.setAccessible(true);
                    field.set(object, value);
                    return object;
                } catch (Exception e) {
                    throw new Exception(
                            "Can not append value to Object for type["
                                    + object.getClass() + "]\n "
                                    + e.getMessage());
                } finally {
                    field.setAccessible(originalAccess);
                }

            } catch (Exception e) {
                throw new Exception("Can not append value to Object for type["
                        + object.getClass() + "]\n has no such field[" + key
                        + "]");
            }
        }
    }


    /**
     * Get field type from Class by field name.
     *
     * @param clz   The Class
     * @param fname The field name
     * @return The field type
     * @throws Exception
     */
    public static Class<?> getFieldType(Class<?> clz, String fname)
            throws Exception {
        Field field = clz.getDeclaredField(fname);
        return field.getType();
    }


    /**
     * Check if a class is defined in JDK system.
     *
     * @param cls The class to check
     * @return
     * @throws Exception
     */
    public static boolean isJDKSystemType(Class<?> cls) throws Exception {
        ClassLoader classLoader = cls.getClassLoader();
        if (classLoader == null)
            return true;
        if (classLoader.equals(ClassLoader.getSystemClassLoader().getParent())) {
            return true;
        }
        return false;
    }

    /**
     * Get relative path against to class-path by a defined class and a path relative against the class.
     *
     * @param clz  The defined class
     * @param path The relative path relative against the class
     * @return the path relative to class-path
     */
    public static String getRelativePath(Class clz, String path) {
        String name = clz.getPackage().getName();
        String pkgDir = name.replace('.', '/');
        if (StringUtils.isEmpty(path) || ".".equals(path) || "./".equals(path)) {
            return pkgDir;
        }
        String paths = pkgDir + "/" + path;
        LinkedList<String> entryList = new LinkedList<String>();
        String[] pEntries = paths.split("/|\\\\");
        for (int i = 0; i < pEntries.length; i++) {
            String entry = pEntries[i];
            if (".".equals(entry) || "".equals(entry)) {
                continue;
            } else if ("..".equals(entry)) {
                if (entryList.size() > 0) {
                    entryList.removeLast();
                }
            } else {
                entryList.addLast(entry);
            }
        }
        StringBuilder relativePath = new StringBuilder("");
        for (int i = 0; i < entryList.size(); i++) {
            if (i == entryList.size() - 1) {
                relativePath.append(entryList.get(i));
            } else {
                relativePath.append(entryList.get(i) + "/");
            }
        }
        return relativePath.toString();
    }

    /**
     * List directory contents for a resource folder. Not recursive. This is
     * basically a brute-force implementation. Works for regular files and also
     * JARs.
     *
     * @param clz  Any java class that lives in the same place as the resources
     *             you want.
     * @param path Should end with "/", but not start with one.
     * @return Just the name of each member item, not the full paths.
     * @throws URISyntaxException
     * @throws IOException
     */
    @SuppressWarnings("resource")
    public static List<Object> getResourceListing(Class clz, String path,
                                                  boolean recursive, boolean includingDir, String encoding)
            throws URISyntaxException, IOException {
        URL dirURL = clz.getClassLoader().getResource(path);
        if (dirURL != null && dirURL.getProtocol().equals("file")) {
            /* A file path: easy enough */
            List<File> files = FileUtils.getFiles(dirURL, recursive, includingDir);
            if (isEmpty(files)) {
                return Collections.emptyList();
            }
            List<Object> resources = new ArrayList<Object>();
            for (File file : files) {
                resources.add(file.toURI().toURL());
            }
            return resources;
        }

        if (dirURL == null) {
            /*
             * In case of a jar file, we can't actually find a directory. Have
			 * to assume the same jar as clazz.
			 */
            String me = clz.getName().replace(".", "/") + ".class";
            dirURL = clz.getClassLoader().getResource(me);
        }

        if (dirURL == null) {
            return Collections.emptyList();
        }

        if ("jar".equals(dirURL.getProtocol())) {
            /* A JAR path */
            String jarPath = dirURL.getPath().substring(5,
                    dirURL.getPath().indexOf("!")); // strip out only the JAR
            // file
            JarFile jar = new JarFile(URLDecoder.decode(jarPath, encoding));
            Enumeration<JarEntry> entries = jar.entries(); // gives ALL entries
            // in jar
            List<Object> resources = new ArrayList<Object>();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String name = entry.getName();
                if (name.startsWith(path)) { // filter according to the path
                    String eName = name.substring(path.length());
                    if (eName.endsWith("/")) {
                        eName = eName.substring(0, eName.length() - 1);
                        if (includingDir) {
                            if (recursive) {
                                resources.add(entry);
                            } else {
                                if (!eName.contains("/")) {
                                    resources.add(entry);
                                }
                            }
                        }
                    } else {
                        if (recursive) {
                            resources.add(entry);
                        } else {
                            if (!eName.contains("/")) {
                                resources.add(entry);
                            }
                        }
                    }
                }
            }
            return resources;
        }

        throw new UnsupportedOperationException(
                "Cannot list resources for URL " + dirURL);
    }

    /**
     * Get value from map-like container by key.
     *
     * @param object The map-like container
     * @param key    The key
     * @return The value getHttpRequest from container by key
     * @throws Exception
     */
    public static Object getFromMapLikeObject(Object object, String key)
            throws Exception {
        if (Map.class.isInstance(object)) {
            return ((Map) object).get(key);
        } else if (String.class.equals(object.getClass())
                || isBaseType(object.getClass())) {
            throw new Exception("Can not getHttpRequest value from Object for type["
                    + object.getClass() + "]");
        } else {
            try {
                Field field = object.getClass().getDeclaredField(key);
                boolean originalAccess = field.isAccessible();
                try {
                    field.setAccessible(true);
                    return field.get(object);
                } catch (Exception e) {
                    throw new Exception(
                            "Can not getHttpRequest value from Object for type["
                                    + object.getClass() + "]\n "
                                    + e.getMessage());
                } finally {
                    field.setAccessible(originalAccess);
                }

            } catch (Exception e) {
                throw new Exception("Can not getHttpRequest value from Object for type["
                        + object.getClass() + "]\n has no such field[" + key
                        + "]");
            }
        }
    }

    /**
     * Get field parameters type from Class definition by field name.
     *
     * @param clz   The Class
     * @param fname The field name
     * @return The field parameters
     * @throws Exception
     */
    public static Class<?>[] getFieldParameterTypes(Class<?> clz, String fname)
            throws Exception {
        Field field = clz.getDeclaredField(fname);
        Type type = field.getGenericType();
        if (ParameterizedType.class.isInstance(type)) {
            ParameterizedType pType = (ParameterizedType) type;
            Type[] pts = pType.getActualTypeArguments();
            int length = pts.length;
            Class<?>[] clzs = new Class<?>[length];
            for (int i = 0; i < length; i++) {
                clzs[i] = (Class<?>) pts[i];
            }
            return clzs;
        }
        return new Class<?>[]{Object.class, Object.class};
    }

    public static String[] getParameterNames(AccessibleObject methodOrConstructor) {
        String[] paramNames = null;
        if (SpringUtils.isSpringInClasspath()) {
            paramNames = SpringUtils.getParameterNames(methodOrConstructor);
        }
        if (paramNames == null) {
            paramNames = INSTANCE_PARAMETER_FINDER.findParameterNames(methodOrConstructor, false);
        }
        return paramNames;
    }


    /**
     * {@link Type#toString()} value is the fully qualified class name prefixed
     * with {@link ReflectionUtils#TYPE_NAME_PREFIX}. This method will substring it, for it to be eligible
     * for {@link Class#forName(String)}.
     *
     * @param type the {@code Type} value whose class name is needed.
     * @return {@code String} class name of the invoked {@code type}.
     * @see {@link ReflectionUtils#getClass()}
     */
    public static String getClassName(Type type) {
        if (type == null) {
            return "";
        }
        String className = type.toString();
        if (className.startsWith(TYPE_CLASS_NAME_PREFIX)) {
            className = className.substring(TYPE_CLASS_NAME_PREFIX.length());
        } else if (className.startsWith(TYPE_INTERFACE_NAME_PREFIX)) {
            className = className.substring(TYPE_INTERFACE_NAME_PREFIX.length());
        }
        return className;
    }

    /**
     * Returns the {@code Class} object associated with the given {@link Type}
     * depending on its fully qualified name.
     *
     * @param type the {@code Type} whose {@code Class} is needed.
     * @return the {@code Class} object for the class with the specified name.
     * @throws ClassNotFoundException if the class cannot be located.
     * @see {@link ReflectionUtils#getClassName(Type)}
     */
    public static Class<?> getClass(Type type)
            throws ClassNotFoundException {
        String className = getClassName(type);
        if (className == null || className.isEmpty()) {
            return null;
        }
        return Class.forName(className);
    }

    public static Class<?>[] getGenericClasses(Type genericType) throws ClassNotFoundException {
        if (genericType == null) {
            return null;
        }
        if (ParameterizedType.class.isAssignableFrom(genericType.getClass())) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            Type[] types = parameterizedType.getActualTypeArguments();
            Class<?>[] genericClasses = new Class<?>[types.length];
            for (int i = 0; i < types.length; i++) {
                Type type = types[i];
                Class<?> typeClz = getClass(type);
                genericClasses[i] = typeClz;
            }
            return genericClasses;
        } else {
            return null;
        }
    }

    /**
     * Creates a new instance of the class represented by this {@code Type} object.
     *
     * @param type the {@code Type} object whose its representing {@code Class} object
     *             will be instantiated.
     * @return a newly allocated instance of the class represented by
     * the invoked {@code Type} object.
     * @throws ClassNotFoundException if the class represented by this {@code Type} object
     *                                cannot be located.
     * @throws InstantiationException if this {@code Type} represents an abstract class,
     *                                an interface, an array class, a primitive type, or void;
     *                                or if the class has no nullary constructor;
     *                                or if the instantiation fails for some other reason.
     * @throws IllegalAccessException if the class or its nullary constructor is not accessible.
     * @see {@link Class#newInstance()}
     */
    public static Object newInstance(Type type)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> clazz = getClass(type);
        if (clazz == null) {
            return null;
        }
        return clazz.newInstance();
    }

    /**
     * Returns an array of {@code Type} objects representing the actual type
     * arguments to this object.
     * If the returned value is null, then this object represents a non-parameterized
     * object.
     *
     * @param clz the {@code clz} which type arguments are needed.
     * @return an array of {@code Type} objects representing the actual type
     * arguments to this object.
     * @see {@link Class#getGenericSuperclass()}
     * @see {@link ParameterizedType#getActualTypeArguments()}
     */
    public static List<Type> getParameterizedTypes(Type type) {
        List<Type> typeList = new ArrayList<>();
        if (type != null && ParameterizedType.class.isAssignableFrom(type.getClass())) {
            typeList.addAll(Arrays.asList(((ParameterizedType) type).getActualTypeArguments()));
        }
        return typeList;
    }

    /**
     * Returns an array of {@code Type} objects representing the actual type
     * arguments to this object.
     * If the returned value is null, then this object represents a non-parameterized
     * object.
     *
     * @param clz the {@code clz} which type arguments are needed.
     * @return an array of {@code Type} objects representing the actual type
     * arguments to this object.
     * @see {@link Class#getGenericSuperclass()}
     * @see {@link ParameterizedType#getActualTypeArguments()}
     */
    public static List<Type> getParameterizedTypes(Class<?> clz) {
        List<Type> typeList = new ArrayList<>();
        Type superclassType = clz.getGenericSuperclass();
        if (superclassType != null && ParameterizedType.class.isAssignableFrom(superclassType.getClass())) {
            typeList.addAll(Arrays.asList(((ParameterizedType) superclassType).getActualTypeArguments()));
        } else {
            Type[] interfaceTypes = clz.getGenericInterfaces();
            if (interfaceTypes != null) {
                for (Type interfaceType : interfaceTypes) {
                    if (interfaceType != null && ParameterizedType.class.isAssignableFrom(interfaceType.getClass())) {
                        typeList.addAll(Arrays.asList(((ParameterizedType) interfaceType).getActualTypeArguments()));
                    }
                }
            }
        }
        return typeList;
    }

    /**
     * Checks whether a {@code Constructor} object with no parameter types is specified
     * by the invoked {@code Class} object or not.
     *
     * @param clazz the {@code Class} object whose constructors are checked.
     * @return {@code true} if a {@code Constructor} object with no parameter types is specified.
     * @throws SecurityException If a security manager, <i>s</i> is present and any of the
     *                           following conditions is met:
     *                           <ul>
     *                           <li> invocation of
     *                           {@link SecurityManager#checkMemberAccess
     *                           s.checkMemberAccess(this, Member.PUBLIC)} denies
     *                           access to the constructor
     *                           <p>
     *                           <li> the caller's class loader is not the same as or an
     *                           ancestor of the class loader for the current class and
     *                           invocation of {@link SecurityManager#checkPackageAccess
     *                           s.checkPackageAccess()} denies access to the package
     *                           of this class
     *                           </ul>
     * @see {@link Class#getConstructor(Class...)}
     */
    public static boolean hasDefaultConstructor(Class<?> clazz) throws SecurityException {
        Class<?>[] empty = {};
        try {
            clazz.getConstructor(empty);
        } catch (NoSuchMethodException e) {
            return false;
        }
        return true;
    }

    /**
     * Returns a {@code Class} object that identifies the
     * declared class for the field represented by the given {@code String name} parameter inside
     * the invoked {@code Class<?> clazz} parameter.
     *
     * @param clazz the {@code Class} object whose declared fields to be
     *              checked for a certain field.
     * @param name  the field name as {@code String} to be
     *              compared with {@link Field#getName()}
     * @return the {@code Class} object representing the type of given field name.
     * @see {@link Class#getDeclaredFields()}
     * @see {@link Field#getType()}
     */
    public static Class<?> getFieldClass(Class<?> clazz, String name) {
        if (clazz == null || name == null || name.isEmpty()) {
            return null;
        }

        Class<?> propertyClass = null;

        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            if (field.getName().equalsIgnoreCase(name)) {
                propertyClass = field.getType();
                break;
            }
        }

        return propertyClass;
    }

    /**
     * Returns a {@code Class} object that identifies the
     * declared class as a return type for the method represented by the given
     * {@code String name} parameter inside the invoked {@code Class<?> clazz} parameter.
     *
     * @param clazz the {@code Class} object whose declared methods to be
     *              checked for the wanted method name.
     * @param name  the method name as {@code String} to be
     *              compared with {@link Method#getName()}
     * @return the {@code Class} object representing the return type of the given method name.
     * @see {@link Class#getDeclaredMethods()}
     * @see {@link Method#getReturnType()}
     */
    public static Class<?> getMethodReturnType(Class<?> clazz, String name) {
        if (clazz == null || name == null || name.isEmpty()) {
            return null;
        }

        name = name.toLowerCase();
        Class<?> returnType = null;

        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getName().equals(name)) {
                returnType = method.getReturnType();
                break;
            }
        }

        return returnType;
    }

    /**
     * Extracts the enum constant of the specified enum class with the
     * specified name. The name must match exactly an identifier used
     * to declare an enum constant in the given class.
     *
     * @param clazz the {@code Class} object of the enum type from which
     *              to return a constant.
     * @param name  the name of the constant to return.
     * @return the enum constant of the specified enum type with the
     * specified name.
     * @throws IllegalArgumentException if the specified enum type has
     *                                  no constant with the specified name, or the specified
     *                                  class object does not represent an enum type.
     * @see {@link Enum#valueOf(Class, String)}
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static Object getEnumConstant(Class<?> clazz, String name) {
        if (clazz == null || name == null || name.isEmpty()) {
            return null;
        }
        return Enum.valueOf((Class<Enum>) clazz, name);
    }

    public static boolean isPresent(String className) {
        return isPresent(className, Thread.currentThread().getContextClassLoader());
    }

    /**
     * Determine whether the {@link Class} identified by the supplied name is present
     * and can be loaded. Will return {@code false} if either the class or
     * one of its dependencies is not present or cannot be loaded.
     *
     * @param className   the name of the class to check
     * @param classLoader the class loader to use
     *                    (may be {@code null}, which indicates the default class loader)
     * @return whether the specified class is present
     */
    public static boolean isPresent(String className, ClassLoader classLoader) {
        try {
            classLoader.loadClass(className);
            return true;
        } catch (Throwable ex) {
            // Class or one of its dependencies is not present...
            return false;
        }
    }

    public static void setFieldValue(Field field, Object fieldValue, Object target) {
        int modifiers = field.getModifiers();
        if (Modifier.isStatic(modifiers)) {
            target = field.getDeclaringClass();
        }
        try {
            if (Modifier.isPublic(modifiers)) {
                field.set(target, fieldValue);
            } else {
                boolean originAccessible = field.isAccessible();
                try {
                    field.setAccessible(true);
                    field.set(target, fieldValue);
                } finally {
                    field.setAccessible(originAccessible);
                }
            }
        } catch (ReflectiveOperationException e) {
            //ignored
        }
    }

    public static Object getValue(String value, Class<?> clz) {
        if (StringUtils.isBlank(value)) {
            return value;
        }
        if (byte.class == clz || Byte.class == clz) {
            return Byte.valueOf(value);
        } else if (short.class == clz || Short.class == clz) {
            return Short.valueOf(value);
        } else if (int.class == clz || Integer.class == clz) {
            return Integer.valueOf(value);
        } else if (long.class == clz || Long.class == clz) {
            return Long.valueOf(value);
        } else if (float.class == clz || Float.class == clz) {
            return Float.valueOf(value);
        } else if (double.class == clz || Double.class == clz) {
            return Double.valueOf(value);
        } else if (boolean.class == clz || Boolean.class == clz) {
            return Boolean.valueOf(value);
        } else if (char.class == clz || Character.class == clz) {
            return value.charAt(0);
        } else if (BigDecimal.class == clz) {
            return new BigDecimal(value);
        } else if (BigInteger.class == clz) {
            return new BigInteger(value);
        }
        return value;
    }

    public static Object getFieldValue(String name, Object target) {
        Object fieldValue = null;
        try {
            Field field = null;
            if (Class.class == target) {
                field = ((Class) target).getDeclaredField(name);
            } else {
                field = target.getClass().getDeclaredField(name);
            }
            fieldValue = getFieldValue(field, target);
        } catch (ReflectiveOperationException e) {
            //ignored
        }
        return fieldValue;
    }

    public static Object getFieldValue(Field field, Object target) {
        Object fieldValue = null;
        boolean originAccessible = field.isAccessible();
        try {
            if (originAccessible) {
                fieldValue = field.get(target);
            } else {
                field.setAccessible(true);
                fieldValue = field.get(target);
            }
        } catch (ReflectiveOperationException e) {
            //ignored
        } finally {
            if(!originAccessible) {
                field.setAccessible(originAccessible);
            }
        }
        return fieldValue;
    }
}
